﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CommonLib;
using CommonLib.Engine;
using CommonLib.Geometry;
using CommonLib.Materials;
using CommonLib.Common;
using Microsoft.DirectX;
using CommonLib.Primitives;

namespace CookTorranceShading
{
    public class PluginCookTorrance : IPluginShading
    {
        public string Description
        {
            get { return "Закраска Кука-Торренса"; }
        }
        public string ID
        {
            get { return "7F92B9E5-2141-4D1C-BD1C-6C286386464D"; }
        }
        private float GetCoefD(Vector3 h, Vector3 normal, float m = 0.3f)
        {
            var hn = Vector3.Dot(h, normal);
            var hn2 = Math.Pow(hn, 2);
            var hn4 = Math.Pow(hn, 4);
            var d1 = Math.Exp((hn2 - 1) / (m * m * hn2)) / (4 * m * m * hn4);

            var angle = Math.Acos(Vector3.Dot(h, normal)/h.Length()*normal.Length());

            var d = Math.Exp(-1*Math.Pow(Math.Tan(angle)/m, 2))/(4*m*m*Math.Pow(Math.Cos(angle), 4));


            return (float)d;
        }
        private float GetCoefG(Vector3 normal, Vector3 camera, Vector3 light)
        {
            Vector3 h = Vector3.Add(light, camera);
            h.Normalize();
            var hn = Vector3.Dot(h, normal);
            var vn = Vector3.Dot(camera, normal);
            var vh = Vector3.Dot(camera, h);
            var ln = Vector3.Dot(light, normal);
            var hl = Vector3.Dot(h, light);
            var g = Math.Min(Math.Min(1, 2 * hn * vn / vh), 2 * hn * ln / vh);

            var g1 = Math.Min(Math.Min(1, 2*hn*ln)/hl, 2*hn*vn/hl);
            return g1;
        }
        private float GetCoefF(Vector3 normal, Vector3 camera)
        {
            var f0 = 0.989;
            var f =1 / (1 + Vector3.Dot(normal, camera));

            var f1 =(float) (f0 + Math.Pow(1 - Vector3.Dot(normal, camera), 5)*(1 - f0));
            return f1;
        }
        private float GetCoefSpec(Vector3 normal, Vector3 camera, Vector3 light)
        {
            return (GetCoefF(normal, camera) * GetCoefD(Vector3.Add(light, camera), normal) *
                    GetCoefG(normal, camera, light)) / Vector3.Dot(normal, camera);
        }
        public Color Shade(Ray ray, Scene scn)
        {
            Intersections intersections = Utilities.GetIntersections(ray, scn);
            HitInfo firstIntersection = intersections.GetBestIntersection();
            if (firstIntersection == null)
                return scn.Background;
            var clr = new Color();
            var v = new Vector3(ray.Dir.X * (-1), ray.Dir.Y * (-1), ray.Dir.Z * (-1)); //вектор, направленный в сторону камеры
            v.Normalize();
            var hitObj = firstIntersection.HitObj as Shape;
            // ReSharper disable PossibleNullReferenceException
            Point3D p = Utilities.GetTransformPoint(firstIntersection.HitPoint, hitObj.TransformMatrix);
            // ReSharper restore PossibleNullReferenceException
            Vector3 normal = Utilities.GetTransformVector(firstIntersection.HitNormal, hitObj.InvTransformMatrix);
            normal.Normalize();
            clr.Add(hitObj.MaterialShape.AmbientColor.Multiply(GetCoefF(normal, v)));//добавляем ФОНОВУЮ составляющую

            //начинаем строить тени
            var feeler = new Ray { Start = new Point3D(p.X + 0.01f * v.X, p.Y + 0.01f * v.Y, p.Z + 0.01f * v.Z) };
            //щуп теней исходит из точки удара в направлении источников света
            foreach (var light in scn.Lights)
            {
                feeler.Dir = new Vector3(light.Position.X - p.X, light.Position.Y - p.Y, light.Position.Z - p.Z);
                if (Utilities.IsShadow(feeler, scn))
                    continue;
                var s = feeler.Dir;//беру направление щупа, чтоб заново не считать вектор
                s.Normalize();
                float mDots = Vector3.Dot(s, normal);//коэффициент ламберта
                //if (mDots > 0)
                //{
                    var diffuse = hitObj.MaterialShape.DiffuseColor.Multiply(light.Color).Multiply(mDots * GetCoefF(normal, v));
                    clr.Add(diffuse);
                //}
                var specColor =
                    hitObj.MaterialShape.SpecularColor.Multiply(light.Color).Multiply(0.0001f * GetCoefSpec(normal, v, s));
                clr.Add(specColor);
            }
            return clr;
        }
    }
}
